﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;

namespace Lightray.Hattrick.ChppLib
{
    public class FileSystemStorage<T> 
    {
        private DirectoryInfo _storageRoot;
        private const string ENTRY_EXTENTION = ".entry";

        public FileSystemStorage(string storagePath, Guid storageId)
        {
            _storageRoot = new DirectoryInfo(Path.Combine(storagePath, storageId.ToString()));
            if (!_storageRoot.Exists)
                Directory.CreateDirectory(storageId.ToString());
        }

        public static Guid CreateNewStorage(string storagePath)
        {
            Guid storageId = Guid.NewGuid();
            DirectoryInfo storageRoot = Directory.CreateDirectory(storageId.ToString());

            return storageId;
        }

        private DirectoryInfo createHierarchyPath(string hierarchy)
        {
            DirectoryInfo path = _storageRoot;
            foreach (var level in hierarchy.Split('/'))
            {
                if (string.IsNullOrEmpty(level))
                    continue;
                if (level.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
                    throw new ArgumentException("Hierarchy cannot contain chars", "hierarchy");
                path = path.CreateSubdirectory(level);
            }

            return path;
        }

        private DirectoryInfo traverseHierarchyPath(string hierarchy)
        {
            string path = _storageRoot.FullName;
            foreach (var level in hierarchy.Split('/'))
            {
                if (string.IsNullOrEmpty(level))
                    continue;
                if (level.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
                    throw new ArgumentException("Hierarchy contains invalid chars", "hierarchy");
                path = Path.Combine(path,level);
            }

            if (!Directory.Exists(path))
                throw new ArgumentException("Hierarchy supplied does not exist", "hierarchy");

            return new DirectoryInfo(path);                    
        }
       
        private T readEntryFromStorage(string entryFilePath)
        {
            using (FileStream fs = File.OpenRead(entryFilePath))
            {
                using (GZipStream gzip = new GZipStream(fs, CompressionMode.Decompress))
                {
                    BinaryFormatter bf = new BinaryFormatter();
                    bf.Binder = new DeserializationBinder();
                    return (T)bf.Deserialize(gzip);
                }
            }
        }

        public void ReadEntry(string entryName, string hierarchy, out T output)
        {
            try
            {
                DirectoryInfo entryPath = traverseHierarchyPath(hierarchy);
                FileInfo entryFile = entryPath.GetFiles(Path.ChangeExtension(entryName, ENTRY_EXTENTION)).First();
                output = readEntryFromStorage(entryFile.FullName);
            }
            catch (Exception inner) 
            {
                throw new FileSystemStorageException("ReadEntry failure", inner);
            }
        }
     
        public void ReadEntry(string entryName, out T output)
        {
            ReadEntry(entryName, string.Empty, out output);
        }

        public void Write(string entryName, T content)
        {
            Write(entryName, string.Empty, content);
        }

        public void Write(string entryName, string hierarchy, T content)
        {
            DirectoryInfo entryPath = createHierarchyPath(hierarchy);
            string entryFile = Path.ChangeExtension(Path.Combine(entryPath.FullName, entryName), ENTRY_EXTENTION);
            using (FileStream fs = File.OpenWrite(entryFile))
            {
                using (GZipStream gzip = new GZipStream(fs, CompressionMode.Compress))
                {
                    BinaryFormatter bf = new BinaryFormatter();
                    bf.Serialize(gzip, content);
                }
            }
        }

        public IEnumerable<string> ListEntries()
        {
            return ListEntries(string.Empty); 
        }

        public IEnumerable<string> ListEntries(string hierarchy)
        {
            try
            {
                DirectoryInfo entryPath = traverseHierarchyPath(hierarchy);
                return entryPath.GetFiles("*" + ENTRY_EXTENTION).Select(fileInfo => Path.GetFileNameWithoutExtension(fileInfo.Name));
            }
            catch (Exception inner)
            {
                throw new FileSystemStorageException("ListEntries failure", inner);
            }
        }    
    }
}
